// Copyright (c) 2021-2025 ByteDance Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//

// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.

#include "shadowhook.h"

#include <ctype.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

#include "bytesig.h"
#include "sh_enter.h"
#include "sh_errno.h"
#include "sh_hub.h"
#include "sh_island.h"
#include "sh_linker.h"
#include "sh_log.h"
#include "sh_recorder.h"
#include "sh_safe.h"
#include "sh_sig.h"
#include "sh_switch.h"
#include "sh_task.h"
#include "sh_util.h"
#include "xdl.h"

#define GOTO_ERR(errnum) \
  do {                   \
    r = errnum;          \
    goto err;            \
  } while (0)

static bool shadowhook_disable = false;
static int shadowhook_init_errno = SHADOWHOOK_ERRNO_UNINIT;
static shadowhook_mode_t shadowhook_default_mode = SHADOWHOOK_MODE_SHARED;

const char *shadowhook_get_version(void) {
  return "shadowhook version " SHADOWHOOK_VERSION;
}

int shadowhook_init(shadowhook_mode_t default_mode, bool debuggable) {
  bool do_init = false;

#define GOTO_END(errnum)            \
  do {                              \
    shadowhook_init_errno = errnum; \
    goto end;                       \
  } while (0)

  if (__predict_true(SHADOWHOOK_ERRNO_UNINIT == shadowhook_init_errno)) {
    static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    pthread_mutex_lock(&lock);
    if (__predict_true(SHADOWHOOK_ERRNO_UNINIT == shadowhook_init_errno)) {
      do_init = true;
      if (__predict_false(sh_errno_is_invalid())) GOTO_END(SHADOWHOOK_ERRNO_INIT_ERRNO);
      if (__predict_false(shadowhook_disable)) GOTO_END(SHADOWHOOK_ERRNO_DISABLED);
      if (__predict_false(default_mode < 0 || default_mode > 2)) GOTO_END(SHADOWHOOK_ERRNO_INVALID_ARG);
      shadowhook_default_mode = default_mode;
      sh_log_set_debuggable(debuggable);
      sh_util_init();
      if (__predict_false(0 != bytesig_init(SIGSEGV))) GOTO_END(SHADOWHOOK_ERRNO_INIT_SIGSEGV);
      if (__predict_false(0 != bytesig_init(SIGBUS))) GOTO_END(SHADOWHOOK_ERRNO_INIT_SIGBUS);
      if (__predict_false(0 != sh_safe_init())) GOTO_END(SHADOWHOOK_ERRNO_INIT_SAFE);
      sh_island_init();
      sh_enter_init();
      sh_switch_init();
      if (__predict_false(0 != sh_linker_init())) GOTO_END(SHADOWHOOK_ERRNO_INIT_LINKER);
      if (__predict_false(0 != sh_task_init())) GOTO_END(SHADOWHOOK_ERRNO_INIT_TASK);

#undef GOTO_END

      shadowhook_init_errno = SHADOWHOOK_ERRNO_OK;
    }
  end:
    pthread_mutex_unlock(&lock);
  }

  const char *mode_str;
  if (SHADOWHOOK_MODE_SHARED == default_mode)
    mode_str = "SHARED";
  else if (SHADOWHOOK_MODE_UNIQUE == default_mode)
    mode_str = "UNIQUE";
  else
    mode_str = "MULTI";
  SH_LOG_ALWAYS_SHOW("%s: shadowhook init(default_mode: %s, debuggable: %s), return: %d, real-init: %s",
                     shadowhook_get_version(), mode_str, debuggable ? "true" : "false", shadowhook_init_errno,
                     do_init ? "yes" : "no");
  SH_ERRNO_SET_RET_ERRNUM(shadowhook_init_errno);
}

int shadowhook_get_init_errno(void) {
  return shadowhook_init_errno;
}

shadowhook_mode_t shadowhook_get_mode(void) {
  return shadowhook_default_mode;
}

bool shadowhook_get_debuggable(void) {
  return sh_log_get_debuggable();
}

void shadowhook_set_debuggable(bool debuggable) {
  sh_log_set_debuggable(debuggable);
}

bool shadowhook_get_recordable(void) {
  return sh_recorder_get_recordable();
}

void shadowhook_set_recordable(bool recordable) {
  sh_recorder_set_recordable(recordable);
}

bool shadowhook_get_disable(void) {
  return shadowhook_disable;
}

void shadowhook_set_disable(bool disable) {
  SH_LOG_ALWAYS_SHOW("shadowhook set disable = %s", disable ? "TRUE" : "FALSE");
  shadowhook_disable = disable;
}

int shadowhook_get_errno(void) {
  return sh_errno_get();
}

const char *shadowhook_to_errmsg(int error_number) {
  return sh_errno_to_errmsg(error_number);
}

static bool shadowhook_check_flags_valid(uint32_t flags) {
#define MODE_MASK 7  // 0b111
  uint32_t mode = flags & MODE_MASK;
  return mode == SHADOWHOOK_HOOK_DEFAULT || mode == SHADOWHOOK_HOOK_WITH_SHARED_MODE ||
         mode == SHADOWHOOK_HOOK_WITH_UNIQUE_MODE || mode == SHADOWHOOK_HOOK_WITH_MULTI_MODE;
}

static bool shadowhook_check_record_name_valid(char *name) {
  if (NULL == name) return true;

  size_t len = strlen(name);
  if (0 == len) return false;
  for (size_t i = 0; i < len; i++) {
    if (isspace((int)(name[i]))) return false;
  }
  return true;
}

static void *shadowhook_hook_addr_impl(const char *api_name, void *target_addr, void *new_addr,
                                       void **orig_addr, uint32_t flags, bool is_sym_addr, bool is_proc_start,
                                       uintptr_t caller_addr, char *record_lib_name, char *record_sym_name) {
  SH_LOG_INFO("shadowhook: %s(%s, %s, %p, %p, %" PRIu32 ") ...", api_name,
              NULL == record_lib_name ? "unknown" : record_lib_name,
              NULL == record_sym_name ? "unknown" : record_sym_name, target_addr, new_addr, flags);
  sh_errno_reset();

  int r;
  if (__predict_false(NULL == target_addr || NULL == new_addr)) GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
  if (__predict_false(!shadowhook_check_flags_valid(flags))) GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
  if (__predict_false((flags & SHADOWHOOK_HOOK_WITH_MULTI_MODE) && NULL == orig_addr))
    GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
  if (__predict_false(!shadowhook_check_record_name_valid(record_lib_name)))
    GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
  if (__predict_false(!shadowhook_check_record_name_valid(record_sym_name)))
    GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
  if (__predict_false(shadowhook_disable)) GOTO_ERR(SHADOWHOOK_ERRNO_DISABLED);
  if (__predict_false(SHADOWHOOK_ERRNO_OK != shadowhook_init_errno)) GOTO_ERR(shadowhook_init_errno);

  // create task
  sh_task_t *task = sh_task_create_hook_by_target_addr(
      (uintptr_t)target_addr, (uintptr_t)new_addr, (uintptr_t *)orig_addr, flags, is_sym_addr, is_proc_start,
      (uintptr_t)caller_addr, record_lib_name, record_sym_name);
  if (NULL == task) GOTO_ERR(SHADOWHOOK_ERRNO_OOM);

  // do hook
  r = sh_task_do(task);
  if (0 != r) {
    sh_task_destroy(task);
    GOTO_ERR(r);
  }

  // OK
  SH_LOG_INFO("shadowhook: %s(%p, %p, %" PRIu32 ") OK. return: %p", api_name, target_addr, new_addr, flags,
              (void *)task);
  SH_ERRNO_SET_RET(SHADOWHOOK_ERRNO_OK, (void *)task);

err:
  SH_LOG_ERROR("shadowhook: %s(%p, %p, %" PRIu32 ") FAILED. %d - %s", api_name, target_addr, new_addr, flags,
               r, sh_errno_to_errmsg(r));
  SH_ERRNO_SET_RET_NULL(r);
}

void *shadowhook_hook_func_addr(void *func_addr, void *new_addr, void **orig_addr) {
  const void *caller_addr = __builtin_return_address(0);
  return shadowhook_hook_addr_impl("hook_func_addr", func_addr, new_addr, orig_addr, SHADOWHOOK_HOOK_DEFAULT,
                                   false, true, (uintptr_t)caller_addr, NULL, NULL);
}

void *shadowhook_hook_func_addr_2(void *func_addr, void *new_addr, void **orig_addr, uint32_t flags, ...) {
  const void *caller_addr = __builtin_return_address(0);
  char *record_lib_name = NULL;
  char *record_sym_name = NULL;
  if (flags & SHADOWHOOK_HOOK_RECORD) {
    va_list args;
    va_start(args, flags);
    record_lib_name = va_arg(args, char *);
    record_sym_name = va_arg(args, char *);
    va_end(args);
  }
  return shadowhook_hook_addr_impl("hook_func_addr", func_addr, new_addr, orig_addr, flags, false, true,
                                   (uintptr_t)caller_addr, record_lib_name, record_sym_name);
}

void *shadowhook_hook_sym_addr(void *sym_addr, void *new_addr, void **orig_addr) {
  const void *caller_addr = __builtin_return_address(0);
  return shadowhook_hook_addr_impl("hook_sym_addr", sym_addr, new_addr, orig_addr, SHADOWHOOK_HOOK_DEFAULT,
                                   true, true, (uintptr_t)caller_addr, NULL, NULL);
}

void *shadowhook_hook_sym_addr_2(void *sym_addr, void *new_addr, void **orig_addr, uint32_t flags, ...) {
  const void *caller_addr = __builtin_return_address(0);
  char *record_lib_name = NULL;
  char *record_sym_name = NULL;
  if (flags & SHADOWHOOK_HOOK_RECORD) {
    va_list args;
    va_start(args, flags);
    record_lib_name = va_arg(args, char *);
    record_sym_name = va_arg(args, char *);
    va_end(args);
  }
  return shadowhook_hook_addr_impl("hook_sym_addr", sym_addr, new_addr, orig_addr, flags, true, true,
                                   (uintptr_t)caller_addr, record_lib_name, record_sym_name);
}

static void *shadowhook_hook_sym_name_impl(const char *lib_name, const char *sym_name, void *new_addr,
                                           void **orig_addr, uint32_t flags, shadowhook_hooked_t hooked,
                                           void *hooked_arg, uintptr_t caller_addr) {
  SH_LOG_INFO("shadowhook: hook_sym_name(%s, %s, %p, %" PRIu32 ") ...", lib_name, sym_name, new_addr, flags);
  sh_errno_reset();

  int r;
  if (__predict_false(NULL == lib_name || NULL == sym_name || NULL == new_addr))
    GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
  if (__predict_false(!shadowhook_check_flags_valid(flags))) GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
  if (__predict_false((flags & SHADOWHOOK_HOOK_WITH_MULTI_MODE) && NULL == orig_addr))
    GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
  if (__predict_false(shadowhook_disable)) GOTO_ERR(SHADOWHOOK_ERRNO_DISABLED);
  if (__predict_false(SHADOWHOOK_ERRNO_OK != shadowhook_init_errno)) GOTO_ERR(shadowhook_init_errno);

  // create task
  sh_task_t *task =
      sh_task_create_hook_by_sym_name(lib_name, sym_name, (uintptr_t)new_addr, (uintptr_t *)orig_addr, flags,
                                      hooked, hooked_arg, (uintptr_t)caller_addr);
  if (NULL == task) GOTO_ERR(SHADOWHOOK_ERRNO_OOM);

  // do hook
  r = sh_task_do(task);
  if (0 != r && SHADOWHOOK_ERRNO_PENDING != r) {
    sh_task_destroy(task);
    GOTO_ERR(r);
  }

  // OK
  SH_LOG_INFO("shadowhook: hook_sym_name(%s, %s, %p, %" PRIu32 ") OK. return: %p. %d - %s", lib_name,
              sym_name, new_addr, flags, (void *)task, r, sh_errno_to_errmsg(r));
  SH_ERRNO_SET_RET(r, (void *)task);

err:
  SH_LOG_ERROR("shadowhook: hook_sym_name(%s, %s, %p, %" PRIu32 ") FAILED. %d - %s", lib_name, sym_name,
               new_addr, flags, r, sh_errno_to_errmsg(r));
  SH_ERRNO_SET_RET_NULL(r);
}

void *shadowhook_hook_sym_name(const char *lib_name, const char *sym_name, void *new_addr, void **orig_addr) {
  const void *caller_addr = __builtin_return_address(0);
  return shadowhook_hook_sym_name_impl(lib_name, sym_name, new_addr, orig_addr, SHADOWHOOK_HOOK_DEFAULT, NULL,
                                       NULL, (uintptr_t)caller_addr);
}

void *shadowhook_hook_sym_name_2(const char *lib_name, const char *sym_name, void *new_addr, void **orig_addr,
                                 uint32_t flags) {
  const void *caller_addr = __builtin_return_address(0);
  return shadowhook_hook_sym_name_impl(lib_name, sym_name, new_addr, orig_addr, flags, NULL, NULL,
                                       (uintptr_t)caller_addr);
}

void *shadowhook_hook_sym_name_callback(const char *lib_name, const char *sym_name, void *new_addr,
                                        void **orig_addr, shadowhook_hooked_t hooked, void *hooked_arg) {
  const void *caller_addr = __builtin_return_address(0);
  return shadowhook_hook_sym_name_impl(lib_name, sym_name, new_addr, orig_addr, SHADOWHOOK_HOOK_DEFAULT,
                                       hooked, hooked_arg, (uintptr_t)caller_addr);
}

void *shadowhook_hook_sym_name_callback_2(const char *lib_name, const char *sym_name, void *new_addr,
                                          void **orig_addr, uint32_t flags, shadowhook_hooked_t hooked,
                                          void *hooked_arg) {
  const void *caller_addr = __builtin_return_address(0);
  return shadowhook_hook_sym_name_impl(lib_name, sym_name, new_addr, orig_addr, flags, hooked, hooked_arg,
                                       (uintptr_t)caller_addr);
}

int shadowhook_unhook(void *stub) {
  const void *caller_addr = __builtin_return_address(0);
  SH_LOG_INFO("shadowhook: unhook(%p) ...", stub);
  sh_errno_reset();

  int r;
  if (__predict_false(NULL == stub)) GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
  if (__predict_false(shadowhook_disable)) GOTO_ERR(SHADOWHOOK_ERRNO_DISABLED);
  if (__predict_false(SHADOWHOOK_ERRNO_OK != shadowhook_init_errno)) GOTO_ERR(shadowhook_init_errno);

  sh_task_t *task = (sh_task_t *)stub;
  r = sh_task_undo(task, (uintptr_t)caller_addr);
  sh_task_destroy(task);
  if (0 != r) GOTO_ERR(r);

  // OK
  SH_LOG_INFO("shadowhook: unhook(%p) OK", stub);
  SH_ERRNO_SET_RET_ERRNUM(SHADOWHOOK_ERRNO_OK);

err:
  SH_LOG_ERROR("shadowhook: unhook(%p) FAILED. %d - %s", stub, r, sh_errno_to_errmsg(r));
  SH_ERRNO_SET_RET_FAIL(r);
}

static void *shadowhook_intercept_addr_impl(const char *api_name, void *target_addr,
                                            shadowhook_interceptor_t pre, void *data, uint32_t flags,
                                            bool is_sym_addr, bool is_proc_start, uintptr_t caller_addr,
                                            char *record_lib_name, char *record_sym_name) {
  SH_LOG_INFO("shadowhook: %s(%s, %s, %p, %p, %p %" PRIu32 ") ...", api_name,
              NULL == record_lib_name ? "unknown" : record_lib_name,
              NULL == record_sym_name ? "unknown" : record_sym_name, target_addr, (void *)pre, data, flags);
  sh_errno_reset();

  int r;
  if (__predict_false(NULL == target_addr || NULL == pre)) GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
  if (__predict_false(!shadowhook_check_record_name_valid(record_lib_name)))
    GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
  if (__predict_false(!shadowhook_check_record_name_valid(record_sym_name)))
    GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
  if (__predict_false(shadowhook_disable)) GOTO_ERR(SHADOWHOOK_ERRNO_DISABLED);
  if (__predict_false(SHADOWHOOK_ERRNO_OK != shadowhook_init_errno)) GOTO_ERR(shadowhook_init_errno);

  // create task
  sh_task_t *task =
      sh_task_create_intercept_by_target_addr((uintptr_t)target_addr, pre, data, flags, is_sym_addr,
                                              is_proc_start, caller_addr, record_lib_name, record_sym_name);
  if (NULL == task) GOTO_ERR(SHADOWHOOK_ERRNO_OOM);

  // do intercept
  r = sh_task_do(task);
  if (0 != r) {
    sh_task_destroy(task);
    GOTO_ERR(r);
  }

  // OK
  SH_LOG_INFO("shadowhook: %s(%p, %p, %p) OK. return: %p", api_name, target_addr, (void *)pre, data,
              (void *)task);
  SH_ERRNO_SET_RET(SHADOWHOOK_ERRNO_OK, (void *)task);

err:
  SH_LOG_ERROR("shadowhook: %s(%p, %p, %p) FAILED. %d - %s", api_name, target_addr, (void *)pre, data, r,
               sh_errno_to_errmsg(r));
  SH_ERRNO_SET_RET_NULL(r);
}

void *shadowhook_intercept_instr_addr(void *instr_addr, shadowhook_interceptor_t pre, void *data,
                                      uint32_t flags, ...) {
  const void *caller_addr = __builtin_return_address(0);
  char *record_lib_name = NULL;
  char *record_sym_name = NULL;
  if (flags & SHADOWHOOK_INTERCEPT_RECORD) {
    va_list args;
    va_start(args, flags);
    record_lib_name = va_arg(args, char *);
    record_sym_name = va_arg(args, char *);
    va_end(args);
  }
  return shadowhook_intercept_addr_impl("intercept_instr_addr", instr_addr, pre, data, flags, false, false,
                                        (uintptr_t)caller_addr, record_lib_name, record_sym_name);
}

void *shadowhook_intercept_func_addr(void *func_addr, shadowhook_interceptor_t pre, void *data,
                                     uint32_t flags, ...) {
  const void *caller_addr = __builtin_return_address(0);
  char *record_lib_name = NULL;
  char *record_sym_name = NULL;
  if (flags & SHADOWHOOK_INTERCEPT_RECORD) {
    va_list args;
    va_start(args, flags);
    record_lib_name = va_arg(args, char *);
    record_sym_name = va_arg(args, char *);
    va_end(args);
  }
  return shadowhook_intercept_addr_impl("intercept_func_addr", func_addr, pre, data, flags, false, true,
                                        (uintptr_t)caller_addr, record_lib_name, record_sym_name);
}

void *shadowhook_intercept_sym_addr(void *sym_addr, shadowhook_interceptor_t pre, void *data, uint32_t flags,
                                    ...) {
  const void *caller_addr = __builtin_return_address(0);
  char *record_lib_name = NULL;
  char *record_sym_name = NULL;
  if (flags & SHADOWHOOK_INTERCEPT_RECORD) {
    va_list args;
    va_start(args, flags);
    record_lib_name = va_arg(args, char *);
    record_sym_name = va_arg(args, char *);
    va_end(args);
  }
  return shadowhook_intercept_addr_impl("intercept_sym_addr", sym_addr, pre, data, flags, true, true,
                                        (uintptr_t)caller_addr, record_lib_name, record_sym_name);
}

static void *shadowhook_intercept_sym_name_impl(const char *lib_name, const char *sym_name,
                                                shadowhook_interceptor_t pre, void *data, uint32_t flags,
                                                shadowhook_intercepted_t intercepted, void *intercepted_arg,
                                                uintptr_t caller_addr) {
  SH_LOG_INFO("shadowhook: intercept_sym_name(%s, %s, %p, %p %" PRIu32 ") ...", lib_name, sym_name,
              (void *)pre, data, flags);
  sh_errno_reset();

  int r;
  if (__predict_false(NULL == lib_name || NULL == sym_name || NULL == pre))
    GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
  if (__predict_false(shadowhook_disable)) GOTO_ERR(SHADOWHOOK_ERRNO_DISABLED);
  if (__predict_false(SHADOWHOOK_ERRNO_OK != shadowhook_init_errno)) GOTO_ERR(shadowhook_init_errno);

  // create task
  sh_task_t *task = sh_task_create_intercept_by_sym_name(lib_name, sym_name, pre, data, flags, intercepted,
                                                         intercepted_arg, (uintptr_t)caller_addr);
  if (NULL == task) GOTO_ERR(SHADOWHOOK_ERRNO_OOM);

  // do hook
  r = sh_task_do(task);
  if (0 != r && SHADOWHOOK_ERRNO_PENDING != r) {
    sh_task_destroy(task);
    GOTO_ERR(r);
  }

  // OK
  SH_LOG_INFO("shadowhook: intercept_sym_name(%s, %s, %p, %p) OK. return: %p. %d - %s", lib_name, sym_name,
              (void *)pre, data, (void *)task, r, sh_errno_to_errmsg(r));
  SH_ERRNO_SET_RET(r, (void *)task);

err:
  SH_LOG_ERROR("shadowhook: intercept_sym_name(%s, %s, %p, %p) FAILED. %d - %s", lib_name, sym_name,
               (void *)pre, data, r, sh_errno_to_errmsg(r));
  SH_ERRNO_SET_RET_NULL(r);
}

void *shadowhook_intercept_sym_name(const char *lib_name, const char *sym_name, shadowhook_interceptor_t pre,
                                    void *data, uint32_t flags) {
  const void *caller_addr = __builtin_return_address(0);
  return shadowhook_intercept_sym_name_impl(lib_name, sym_name, pre, data, flags, NULL, NULL,
                                            (uintptr_t)caller_addr);
}

void *shadowhook_intercept_sym_name_callback(const char *lib_name, const char *sym_name,
                                             shadowhook_interceptor_t pre, void *data, uint32_t flags,
                                             shadowhook_intercepted_t intercepted, void *intercepted_arg) {
  const void *caller_addr = __builtin_return_address(0);
  return shadowhook_intercept_sym_name_impl(lib_name, sym_name, pre, data, flags, intercepted,
                                            intercepted_arg, (uintptr_t)caller_addr);
}

int shadowhook_unintercept(void *stub) {
  const void *caller_addr = __builtin_return_address(0);
  SH_LOG_INFO("shadowhook: unintercept(%p) ...", stub);
  sh_errno_reset();

  int r;
  if (__predict_false(NULL == stub)) GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
  if (__predict_false(shadowhook_disable)) GOTO_ERR(SHADOWHOOK_ERRNO_DISABLED);
  if (__predict_false(SHADOWHOOK_ERRNO_OK != shadowhook_init_errno)) GOTO_ERR(shadowhook_init_errno);

  sh_task_t *task = (sh_task_t *)stub;
  r = sh_task_undo(task, (uintptr_t)caller_addr);
  sh_task_destroy(task);
  if (0 != r) GOTO_ERR(r);

  // OK
  SH_LOG_INFO("shadowhook: unintercept(%p) OK", stub);
  SH_ERRNO_SET_RET_ERRNUM(SHADOWHOOK_ERRNO_OK);

err:
  SH_LOG_ERROR("shadowhook: unintercept(%p) FAILED. %d - %s", stub, r, sh_errno_to_errmsg(r));
  SH_ERRNO_SET_RET_FAIL(r);
}

char *shadowhook_get_records(uint32_t item_flags) {
  return sh_recorder_get(item_flags);
}

void shadowhook_dump_records(int fd, uint32_t item_flags) {
  sh_recorder_dump(fd, item_flags);
}

void *shadowhook_dlopen(const char *lib_name) {
  if (__predict_false(shadowhook_disable)) return NULL;
  if (__predict_false(SHADOWHOOK_ERRNO_OK != shadowhook_init_errno)) return NULL;

  void *handle = NULL;
  if (sh_util_get_api_level() >= __ANDROID_API_L__) {
    handle = xdl_open(lib_name, XDL_DEFAULT);
  } else {
    SH_SIG_TRY(SIGSEGV, SIGBUS) {
      handle = xdl_open(lib_name, XDL_DEFAULT);
    }
    SH_SIG_CATCH() {
      SH_LOG_WARN("shadowhook: dlopen crashed - %s", lib_name);
    }
    SH_SIG_EXIT
  }
  return handle;
}

void shadowhook_dlclose(void *handle) {
  if (__predict_false(shadowhook_disable)) return;
  if (__predict_false(SHADOWHOOK_ERRNO_OK != shadowhook_init_errno)) return;

  xdl_close(handle);
}

void *shadowhook_dlsym(void *handle, const char *sym_name) {
  if (__predict_false(shadowhook_disable)) return NULL;
  if (__predict_false(SHADOWHOOK_ERRNO_OK != shadowhook_init_errno)) return NULL;

  void *addr = shadowhook_dlsym_dynsym(handle, sym_name);
  if (NULL == addr) addr = shadowhook_dlsym_symtab(handle, sym_name);
  return addr;
}

void *shadowhook_dlsym_dynsym(void *handle, const char *sym_name) {
  if (__predict_false(shadowhook_disable)) return NULL;
  if (__predict_false(SHADOWHOOK_ERRNO_OK != shadowhook_init_errno)) return NULL;

  void *addr = NULL;
  SH_SIG_TRY(SIGSEGV, SIGBUS) {
    addr = xdl_sym(handle, sym_name, NULL);
  }
  SH_SIG_CATCH() {
    SH_LOG_WARN("shadowhook: dlsym_dynsym crashed - %p, %s", handle, sym_name);
  }
  SH_SIG_EXIT
  return addr;
}

void *shadowhook_dlsym_symtab(void *handle, const char *sym_name) {
  if (__predict_false(shadowhook_disable)) return NULL;
  if (__predict_false(SHADOWHOOK_ERRNO_OK != shadowhook_init_errno)) return NULL;

  void *addr = NULL;
  SH_SIG_TRY(SIGSEGV, SIGBUS) {
    addr = xdl_dsym(handle, sym_name, NULL);
  }
  SH_SIG_CATCH() {
    SH_LOG_WARN("shadowhook: dlsym_symtab crashed - %p, %s", handle, sym_name);
  }
  SH_SIG_EXIT
  return addr;
}

int shadowhook_register_dl_init_callback(shadowhook_dl_info_t pre, shadowhook_dl_info_t post, void *data) {
  if (__predict_false(shadowhook_disable)) SH_ERRNO_SET_RET_FAIL(SHADOWHOOK_ERRNO_DISABLED);
  if (__predict_false(SHADOWHOOK_ERRNO_OK != shadowhook_init_errno))
    SH_ERRNO_SET_RET_FAIL(shadowhook_init_errno);
#if SH_UTIL_COMPATIBLE_WITH_ARM_ANDROID_4_X
  if (__predict_false(sh_util_get_api_level() < __ANDROID_API_L__))
    SH_ERRNO_SET_RET_FAIL(SHADOWHOOK_ERRNO_NOT_SUPPORT);
#endif
  if (__predict_false(NULL == pre && NULL == post)) SH_ERRNO_SET_RET_FAIL(SHADOWHOOK_ERRNO_INVALID_ARG);

  int r;
  if (0 != (r = sh_linker_register_dl_init_callback(pre, post, data))) SH_ERRNO_SET_RET_FAIL(r);
  SH_ERRNO_SET_RET_ERRNUM(SHADOWHOOK_ERRNO_OK);
}

int shadowhook_unregister_dl_init_callback(shadowhook_dl_info_t pre, shadowhook_dl_info_t post, void *data) {
  if (__predict_false(shadowhook_disable)) SH_ERRNO_SET_RET_FAIL(SHADOWHOOK_ERRNO_DISABLED);
  if (__predict_false(SHADOWHOOK_ERRNO_OK != shadowhook_init_errno))
    SH_ERRNO_SET_RET_FAIL(shadowhook_init_errno);
#if SH_UTIL_COMPATIBLE_WITH_ARM_ANDROID_4_X
  if (__predict_false(sh_util_get_api_level() < __ANDROID_API_L__))
    SH_ERRNO_SET_RET_FAIL(SHADOWHOOK_ERRNO_NOT_SUPPORT);
#endif
  if (__predict_false(NULL == pre && NULL == post)) SH_ERRNO_SET_RET_FAIL(SHADOWHOOK_ERRNO_INVALID_ARG);

  int r;
  if (0 != (r = sh_linker_unregister_dl_init_callback(pre, post, data))) SH_ERRNO_SET_RET_FAIL(r);
  SH_ERRNO_SET_RET_ERRNUM(SHADOWHOOK_ERRNO_OK);
}

int shadowhook_register_dl_fini_callback(shadowhook_dl_info_t pre, shadowhook_dl_info_t post, void *data) {
  if (__predict_false(shadowhook_disable)) SH_ERRNO_SET_RET_FAIL(SHADOWHOOK_ERRNO_DISABLED);
  if (__predict_false(SHADOWHOOK_ERRNO_OK != shadowhook_init_errno))
    SH_ERRNO_SET_RET_FAIL(shadowhook_init_errno);
#if SH_UTIL_COMPATIBLE_WITH_ARM_ANDROID_4_X
  if (__predict_false(sh_util_get_api_level() < __ANDROID_API_L__))
    SH_ERRNO_SET_RET_FAIL(SHADOWHOOK_ERRNO_NOT_SUPPORT);
#endif
  if (__predict_false(NULL == pre && NULL == post)) SH_ERRNO_SET_RET_FAIL(SHADOWHOOK_ERRNO_INVALID_ARG);

  int r;
  if (0 != (r = sh_linker_register_dl_fini_callback(pre, post, data))) SH_ERRNO_SET_RET_FAIL(r);
  SH_ERRNO_SET_RET_ERRNUM(SHADOWHOOK_ERRNO_OK);
}

int shadowhook_unregister_dl_fini_callback(shadowhook_dl_info_t pre, shadowhook_dl_info_t post, void *data) {
  if (__predict_false(shadowhook_disable)) SH_ERRNO_SET_RET_FAIL(SHADOWHOOK_ERRNO_DISABLED);
  if (__predict_false(SHADOWHOOK_ERRNO_OK != shadowhook_init_errno))
    SH_ERRNO_SET_RET_FAIL(shadowhook_init_errno);
#if SH_UTIL_COMPATIBLE_WITH_ARM_ANDROID_4_X
  if (__predict_false(sh_util_get_api_level() < __ANDROID_API_L__))
    SH_ERRNO_SET_RET_FAIL(SHADOWHOOK_ERRNO_NOT_SUPPORT);
#endif
  if (__predict_false(NULL == pre && NULL == post)) SH_ERRNO_SET_RET_FAIL(SHADOWHOOK_ERRNO_INVALID_ARG);

  int r;
  if (0 != (r = sh_linker_unregister_dl_fini_callback(pre, post, data))) SH_ERRNO_SET_RET_FAIL(r);
  SH_ERRNO_SET_RET_ERRNUM(SHADOWHOOK_ERRNO_OK);
}

void *shadowhook_get_prev_func(void *func) {
  return sh_hub_get_prev_func(func);
}

void shadowhook_pop_stack(void *return_address) {
  sh_hub_pop_stack(return_address);
}

void shadowhook_allow_reentrant(void *return_address) {
  sh_hub_allow_reentrant(return_address);
}

void shadowhook_disallow_reentrant(void *return_address) {
  sh_hub_disallow_reentrant(return_address);
}

void *shadowhook_get_return_address(void) {
  return sh_hub_get_return_address();
}
